import {
    db, UserModel, DomainModel, SettingModel, Handler, UserNotFoundError, NotFoundError, ForbiddenError, ValidationError, param, PRIV, Types, query, STATUS, Logger
} from 'hydrooj';

const logger = new Logger('score-reward');

const coll = db.collection('coin');
const collgds = db.collection('goods');

interface Bill {
    _id: ObjectId;
    userId: number;
    rootId: number;
    amount: number;
    text: string;
    status: number;
}

interface Goods {
    _id: number;
    name: string;
    price: number;
    num: number;
}

declare module 'hydrooj' {
    interface Model {
        coin: typeof coinModel;
        goods: typeof goodsModel;
    }
    interface Collections {
        bill: Bill;
        goods: Goods;
    }
}

async function inc(userId: number, rootId: number, amount: number, text: string, asset: number, status?: number) {
    await coll.insertOne({ userId, rootId, amount, text, ...(status !== undefined && { status }) });
    // 发放
    await UserModel.inc(userId, 'coin_now', amount);
    if (amount > 0 && asset == 1)
        await UserModel.inc(userId, 'coin_all', amount);
}

async function getUserBill(userId: number): Promise<Paste> {
    const query = userId === 0 ? {} : { userId };
    return await coll.find(query).sort({ _id: -1 });
}

async function getUserRecord(userId: number): Promise<Paste> {
    const query = userId === 0 ? { status: { $gte: 0 } } : { userId, status: { $gte: 0 } };
    return await coll.find(query).sort({ status:-1, _id: -1 });
}


async function BillGet(billId: string): Promise<Bill> {
    return await coll.findOne({ _id: billId });
}

async function BillDel(billId: number): Promise<number> {
    const result = await coll.deleteOne({ _id: billId });
    return result.deletedCount;
}


async function GoodsAdd(name: string, price: number, num: number, goodsId?: number) {
    if (typeof goodsId !== 'number') {
        const [goods] = await collgds.find({}).sort({ _id: -1 }).limit(1).toArray();
        goodsId = Math.max((goods?._id || 0) + 1, 1);
    };
    const result = await collgds.insertOne({
        _id: goodsId,
        name: name,
        price: price,
        num: num
    });
    return result.insertedId;
}

async function GoodsGetMuilt(): Promise<Goods[]> {
    return await collgds.find({});
}

async function GoodsGet(goodsId: number): Promise<Goods> {
    return await collgds.findOne({ _id: goodsId });
}

async function GoodsEdit(goodsId: number, name: string, price: number, num: number): Promise<number> {
    const result = await collgds.updateOne({ _id: goodsId }, {
        $set: {
            name: name,
            price: price,
            num: num
        }
    });
    return result.modifiedCount;
}

async function GoodsDel(goodsId: number): Promise<number> {
    const result = await collgds.deleteOne({ _id: goodsId });
    return result.deletedCount;
}

const coinModel = { inc, getUserBill, getUserRecord, BillGet, BillDel };
const goodsModel = { GoodsAdd, GoodsGetMuilt, GoodsGet, GoodsEdit, GoodsDel };
global.Hydro.model.coin = coinModel;
global.Hydro.model.goods = goodsModel;

//展示所有 
class CoinShowHandler extends Handler {
    @query('page', Types.PositiveInt, true)
    @query('groupName', Types.string, true)
    async get(domainId: string, page = 1, groupName: string) {

        let filter = { coin_now: { $exists: true } };

        const groups = await UserModel.listGroup(domainId);
        if (groupName) {
            const groupInfo = groups.find(g => g.name === groupName);
            if (groupInfo) {
                filter._id = { $in: groupInfo.uids };
            }
        }

        const [dudocs, upcount] = await this.paginate(
            UserModel.getMulti(filter).sort({ coin_now: -1 }),
            page,
            'ranking'
        );
        const udict = await UserModel.getList(domainId, dudocs.map((x) => x._id));
        const udocs = dudocs.map((x) => udict[x._id]);
        this.response.template = 'coin_show.html'; // 返回此页面
        this.response.body = { udocs, upcount, page, groupName, groups };
    }
}

//发放硬币
class CoinIncHandler extends Handler {
    @query('uidOrName', Types.UidOrName, true)
    async get(domainId: string, uidOrName: string) {
        this.response.template = 'coin_inc.html'; // 返回此页面
        this.response.body = { uidOrName };
    }
    @param('uidOrName', Types.UidOrName)
    @param('amount', Types.Int)
    @param('text', Types.String)
    async post(domainId: string, uidOrName: string, amount: number, text: string) {
        amount = parseInt(amount);
        const udoc = await UserModel.getById(domainId, +uidOrName)
            || await UserModel.getByUname(domainId, uidOrName)
            || await UserModel.getByEmail(domainId, uidOrName);
        if (!udoc)
            throw new UserNotFoundError(uidOrName);
        await coinModel.inc(udoc._id, this.user._id, amount, text, 1);
        this.response.redirect = this.url('coin_inc'); 
    }
}

//账单
class CoinBillHandler extends Handler {
    @query('uid', Types.Int, true)
    @query('page', Types.PositiveInt, true)
    async get(domainId: string, uid = this.user._id, page = 1) {
        //管理员能看所有，其他人只能看自己的
        //if (uid != this.user._id)
        //    this.checkPriv(PRIV.PRIV_SET_PERM);
        // id 为 0 即查看所有人
        const udoc = await UserModel.getById(domainId, uid);
        const [bills, upcount] = await this.paginate(
            await coinModel.getUserBill(uid),
            page,
            'ranking'
        );

        // 获取所有相关的用户信息
        const uids = new Set<number>([
            ...bills.map((x) => x.userId),
            ...bills.map((x) => x.rootId),
        ]);
        const udict = await UserModel.getList(domainId, Array.from(uids));

        this.response.template = 'coin_bill.html'; // 返回此页面
        this.response.body = { udoc, bills, upcount, page, udict };
    }
}

//批量导入硬币
class CoinImportHandler extends Handler {
    async get() {
        this.response.body.coins = [];
        this.response.template = 'coin_import.html'; // 返回此页面
    }
    @param('coins', Types.Content)
    @param('draft', Types.Boolean)
    async post(domainId: string, _coins: string, draft: boolean) {
        const coins = _coins.split('\n');
        const udocs: { username: string, amount: number, text: string }[] = [];
        const messages = [];

        for (const i in coins) {
            const u = coins[i];
            if (!u.trim()) continue;
            let [username, amount, text] = u.split('\t').map((t) => t.trim());
            if (username && !amount && !text) {
                const data = u.split(',').map((t) => t.trim());
                [username, amount, text] = data;
            }

            if (!username) continue;
            amount = parseInt(amount);
            if (isNaN(amount)) {
                messages.push(`Line ${+i + 1}:  Invalid amount.`);
                continue;
            }

            // 验证用户是否存在
            const user = await UserModel.getByUname(domainId, username);
            if (!user) {
                messages.push(`Line ${+i + 1}: User ${username} not found.`);
                continue;
            }

            udocs.push({
                username, amount, text
            });
        }

        messages.push(`${udocs.length} coin records found.`);

        if (!draft) {
            for (const udoc of udocs) {
                try {
                    const user = await UserModel.getByUname(domainId, udoc.username);
                    if (!user || !udoc.amount || udoc.amount == 0) continue;
                    await coinModel.inc(user._id, this.user._id, udoc.amount, udoc.text, 1);
                } catch (e) {
                    messages.push(e.message);
                }
            }
        }
        this.response.body.coins = udocs;
        this.response.body.messages = messages;
    }
}

//增加商品
class GoodsAddHandler extends Handler {
    async get() {
        this.response.template = 'goods_add.html'; // 返回此页面
    }
    @param('name', Types.String)
    @param('price', Types.Int)
    @param('num', Types.Int)
    async post(domainId: string, name: string, price: number, num: number) {
        await goodsModel.GoodsAdd(name, price, num);
        this.response.body = { success: true };
    }
}

//管理商品
class GoodsManageHandler extends Handler {
    @query('page', Types.PositiveInt, true)
    async get(domainId: string, page = 1) {
        const [ddocs, dpcount] = await this.paginate(
            await goodsModel.GoodsGetMuilt(),
            page,
            'ranking'
        );
        this.response.template = 'goods_manage.html';
        this.response.body = { ddocs, dpcount, page }
    }
}

class GoodsEditHandler extends Handler {
    @param('id', Types.PositiveInt)
    async get(domainId: string, id: number) {
        const goods = await goodsModel.GoodsGet(id);
        if (!goods) throw new NotFoundError(`商品 ${id} 不存在！`);
        this.response.template = 'goods_edit.html';
        this.response.body = { goods }
    }

    @param('id', Types.PositiveInt)
    @param('name', Types.String)
    @param('price', Types.Int)
    @param('num', Types.Int)
    async postUpdate(domainId: string, id: number, name: string, price: number, num: number) {
        const goods = await goodsModel.GoodsGet(id);
        if (!goods) throw new NotFoundError(`商品 ${id} 不存在！`);
        await goodsModel.GoodsEdit(id, name, price, num);
        this.response.redirect = this.url('goods_manage');
    }

    @param('id', Types.PositiveInt)
    async postDelete(domainId: string, id: number) {
        await goodsModel.GoodsDel(id);
        this.response.redirect = this.url('goods_manage');
    }
}

//兑换商城
class CoinMallHandler extends Handler {
    @query('page', Types.PositiveInt, true)
    async get(domainId: string, page = 1, uid = this.user._id) {
        const udoc = await UserModel.getById(domainId, uid);
        const [ddocs, dpcount] = await this.paginate(
            await goodsModel.GoodsGetMuilt(),
            page,
            'ranking'
        );
        this.response.template = 'coin_mall.html';
        this.response.body = { udoc, ddocs, dpcount, page }
    }
}

//兑换商品
class CoinExchangeHandler extends Handler {
    @param('id', Types.PositiveInt)
    async get(domainId: string, id: string, uid = this.user._id) {
        const goods = await goodsModel.GoodsGet(id);
        const udoc = await UserModel.getById(domainId, uid)
        if (!goods) throw new NotFoundError(`商品 ${id} 不存在！`);
        this.response.template = 'coin_exchange.html';
        this.response.body = { udoc, goods }
    }

    @param('id', Types.PositiveInt)
    @param('num', Types.Int)
    async post(domainId: string, id: number, num: number) {
        const goods = await goodsModel.GoodsGet(id);
        const udoc = await UserModel.getById(domainId, this.user._id);
        if (num <= 0)
            throw new ValidationError(num,'',`商品数量必须大于0`);
        if (goods.num < num)
            throw new ValidationError(num,'',`商品 ${goods.name} 数量不够`);
        const currentCoin = typeof udoc.coin_now === 'number' ? udoc.coin_now : 0;
        if (currentCoin < goods.price * num)
            throw new ValidationError(currentCoin,'',`你的硬币不够`);
        const amount = 0 - goods.price * num;
        const text = '兑换：' + goods.name + '×' + num;
        await coinModel.inc(this.user._id, 1, amount, text, 1, id);
        await collgds.updateOne({ _id: id }, { $inc: { ['num']: 0 - num } });
        this.response.redirect = this.url('coin_myrecord');
    }
}

//我的兑换记录
class CoinMyRecordHandler extends Handler {
    @query('page', Types.PositiveInt, true)
    async get(domainId: string, page = 1, uid = this.user._id) {
        const [bills, upcount] = await this.paginate(
            await coinModel.getUserRecord(uid),
            page,
            'ranking'
        );

        // 获取所有相关的用户信息
        const uids = new Set<number>([
            ...bills.map((x) => x.userId),
            ...bills.map((x) => x.rootId),
        ]);
        const udict = await UserModel.getList(domainId, Array.from(uids));

        this.response.template = 'coin_myrecord.html'; // 返回此页面
        this.response.body = { bills, upcount, page, udict };
    }

    @param('id', Types.ObjectId)
    async post(domainId: string, id: ObjectId) {
        const bill = await coinModel.BillGet(id);
        if (!bill) throw new NotFoundError(`订单 ${id} 不存在！`);
        if (bill.userId !== this.user._id) throw new ValidationError(this.user._id,'',`您无权操作他人订单！`);
        const gid = bill.status;
        if (typeof gid !== 'number' || gid == 0)
            throw new ValidationError('订单','',`订单 ${id} 已确认，无法取消`);
        const amount = 0 - bill.amount;
        const num = parseInt(bill.text.split('×').pop());
        await coinModel.BillDel(id);
        await collgds.updateOne({ _id: gid }, { $inc: { ['num']: num } });
        await UserModel.inc(this.user._id, 'coin_now', amount);
        this.response.body = { success: true };
    }
}

//兑换中心
class CoinRecordHandler extends Handler {
    @query('uid', Types.Int, true)
    @query('page', Types.PositiveInt, true)
    async get(domainId: string, uid = 0, page = 1) {
        const [bills, upcount] = await this.paginate(
            await coinModel.getUserRecord(uid),
            page,
            'ranking'
        );

        // 获取所有相关的用户信息
        const uids = new Set<number>([
            ...bills.map((x) => x.userId),
            ...bills.map((x) => x.rootId),
        ]);
        const udict = await UserModel.getList(domainId, Array.from(uids));

        this.response.template = 'coin_record.html'; // 返回此页面
        this.response.body = { uid, bills, upcount, page, udict };
    }

    @param('id', Types.ObjectId)
    async post(domainId: string, id: ObjectId) {
        const bill = await coinModel.BillGet(id);
        if (!bill) throw new NotFoundError(`订单 ${id} 不存在！`);
        const gid = bill.status;
        if (typeof gid !== 'number' || gid == 0)
            throw new ValidationError('订单','',`订单 ${id} 无法兑换`);
        await coll.updateOne({ _id: id }, { $set: { rootId: this.user._id, text: bill.text, status: 0 } });
        this.response.body = { success: true };
    }
}

//赠送硬币
class CoinGiftHandler extends Handler {
    @query('uidOrName', Types.UidOrName, true)
    async get(domainId: string, uidOrName: string) {
        this.response.template = 'coin_gift.html'; // 返回此页面
        this.response.body = { uidOrName };
    }
    @param('password', Types.Password)
    @param('uidOrName', Types.UidOrName)
    @param('amount', Types.Int)
    async post(domainId: string, password: string, uidOrName: string, amount: number) {
        amount = parseInt(amount);
        if (amount <= 0)
            throw new ValidationError(amount,'',`赠送的硬币必须大于0`);
        const currentCoin = typeof this.user.coin_now === 'number' ? this.user.coin_now : 0;
        if (amount > currentCoin)
            throw new ValidationError(currentCoin,'',`你的硬币不够`);
        const udoc = await UserModel.getById(domainId, +uidOrName)
            || await UserModel.getByUname(domainId, uidOrName)
            || await UserModel.getByEmail(domainId, uidOrName);
        if (!udoc)
            throw new UserNotFoundError(uidOrName);
        if (udoc._id == this.user._id)
            throw new ValidationError(udoc.uname,'',`不能赠送给自己硬币`);
        await this.user.checkPassword(password);
        const text1 = '赠送：送给（' + udoc.uname + '）。' ;
        const text2 = '赠送：来自（' + this.user.uname + '）。';
        await coinModel.inc(this.user._id, udoc._id, 0-amount, text1, 0);
        await coinModel.inc(udoc._id, this.user._id, amount, text2, 0);
        this.response.body = { success: true };
    }
}

// 配置项及路由
export async function apply(ctx: Context) {
    ctx.inject(['setting'], (c) => {
        c.setting.AccountSetting(
            SettingModel.Setting('setting_storage', 'coin_now', 0, 'number', 'coin_now', null, 3),
            SettingModel.Setting('setting_storage', 'coin_all', 0, 'number', 'coin_all', null, 3)
        );
        c.setting.DomainSetting(
            SettingModel.Setting('setting_coin', 'coin_enabled', false, 'boolean', '自动发放硬币', '为该域启用首次 AC 硬币发放功能',0),  
            SettingModel.Setting('setting_coin', 'coin_amount', 2, 'number', '每题硬币数量', '每道题首次 AC 获得的硬币数量',0)  
        );
    });

    ctx.on('record/judge', async (rdoc, updated, pdoc) => {
        try {
            if (rdoc.status !== STATUS.STATUS_ACCEPTED) return;
            if (rdoc.contest) return;
            if (!updated) return;
            if (rdoc.rejudged) return;

            const ddoc = await DomainModel.get(rdoc.domainId);
            const coinEnabled = ddoc?.coin_enabled || false;
            if (!coinEnabled) return;

            const existingStat = await db.collection('record.stat').findOne({
                domainId: rdoc.domainId,
                pid: rdoc.pid,
                uid: rdoc.uid
            });
            if (existingStat) return;

            const coinAmount = +(ddoc?.coin_amount || 2);
            const domainName = ddoc?.name || rdoc.domainId;
            await coinModel.inc( rdoc.uid, ddoc.owner, coinAmount, `答题：${domainName}（ID:${rdoc.pid}）`, 1 );
            logger.info(`User ${rdoc.uid} earned ${coinAmount} coins for first AC on problem ${rdoc.pid} in domain ${domainName}`);
        } catch (error) {
            logger.error('Error in coin reward plugin:', error);
        }
    });

    ctx.Route('coin_show', '/coin/show', CoinShowHandler);
    ctx.Route('coin_inc', '/coin/inc', CoinIncHandler, PRIV.PRIV_SET_PERM);
    ctx.Route('coin_import', '/coin/import', CoinImportHandler, PRIV.PRIV_SET_PERM);
    ctx.Route('coin_bill', '/coin/bill', CoinBillHandler, PRIV.PRIV_USER_PROFILE);
    ctx.Route('coin_mall', '/coin/mall', CoinMallHandler, PRIV.PRIV_USER_PROFILE);
    ctx.Route('coin_myrecord', '/coin/myrecord', CoinMyRecordHandler, PRIV.PRIV_USER_PROFILE);
    ctx.Route('coin_exchange', '/coin/exchange/:id', CoinExchangeHandler, PRIV.PRIV_USER_PROFILE);
    ctx.Route('coin_record', '/coin/record', CoinRecordHandler, PRIV.PRIV_SET_PERM);
    ctx.Route('coin_gift', '/coin/gift', CoinGiftHandler, PRIV.PRIV_USER_PROFILE);
    ctx.Route('goods_add', '/goods/add', GoodsAddHandler, PRIV.PRIV_SET_PERM);
    ctx.Route('goods_manage', '/goods/manage', GoodsManageHandler, PRIV.PRIV_SET_PERM);
    ctx.Route('goods_edit', '/goods/:id/edit', GoodsEditHandler, PRIV.PRIV_SET_PERM);
    ctx.injectUI('UserDropdown', 'coin_bill', { icon: 'bold', displayName: '我的硬币' });
}
